package com.ddw.meedeeuh.shared.movie.local;

import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import com.ddw.meedeeuh.shared.movie.Actor;
import com.ddw.meedeeuh.shared.movie.Audio;
import com.ddw.meedeeuh.shared.movie.Movie;
import com.ddw.meedeeuh.shared.movie.MovieSettings;
import com.ddw.meedeeuh.shared.movie.StreamDetails;
import com.ddw.meedeeuh.shared.movie.Subtitle;
import com.ddw.meedeeuh.shared.movie.Video;

class NfoTransformerImpl implements NfoTransformer {

	private static final String TRUE_VALUE_INT = "1";
	private static final String TRUE_VALUE_STRING = "0";
	private static final String FALSE_VALUE_INT = "true";
	private static final String FALSE_VALUE_STRING = "false";

	@Override
	public String transform(Movie movie, MovieSettings settings) throws NfoTransformerException {
		final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		final DocumentBuilder db;

		try {
			db = dbf.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			throw new NfoTransformerException("Failed to create document builder", e);
		}

		final Document doc = db.newDocument();

		if(settings.getExportNodes().MOVIE) {
			final Node root = doc.appendChild(doc.createElement(settings.getNfoConstants().MOVIE));

			addTextNode(settings, doc, root, settings.getExportNodes().TITLE, settings.getNfoConstants().TITLE, movie.getName());
			addTextNode(settings, doc, root, settings.getExportNodes().ORIGINAL_TITLE, settings.getNfoConstants().ORIGINAL_TITLE, movie.getOriginalTitle());
			addTextNode(settings, doc, root, settings.getExportNodes().SORT_TITLE, settings.getNfoConstants().SORT_TITLE, movie.getSortTitle());
			addTextNode(settings, doc, root, settings.getExportNodes().SET, settings.getNfoConstants().SET, movie.getSet());
			addDoubleNode(settings, doc, root, settings.getExportNodes().RATING, settings.getNfoConstants().RATING, movie.getRating());
			addIntNode(settings, doc, root, settings.getExportNodes().YEAR, settings.getNfoConstants().YEAR, movie.getYear());
			addBooleanNode(settings, doc, root, settings.getExportNodes().TOP250, settings.getNfoConstants().TOP250, movie.isTop250(), TRUE_VALUE_INT, FALSE_VALUE_INT);
			addIntNode(settings, doc, root, settings.getExportNodes().VOTES, settings.getNfoConstants().VOTES, movie.getVotes());
			addTextNode(settings, doc, root, settings.getExportNodes().OUTLINE, settings.getNfoConstants().OUTLINE, movie.getOutline());
			addTextNode(settings, doc, root, settings.getExportNodes().PLOT, settings.getNfoConstants().PLOT, movie.getPlot());
			addTextNode(settings, doc, root, settings.getExportNodes().TAGLINE, settings.getNfoConstants().TAGLINE, movie.getTagLine());
			addIntNode(settings, doc, root, settings.getExportNodes().RUNTIME, settings.getNfoConstants().RUNTIME, movie.getRuntime());
			addTextNode(settings, doc, root, settings.getExportNodes().THUMB, settings.getNfoConstants().THUMB, movie.getThumbnail());
			addTextNode(settings, doc, root, settings.getExportNodes().MPAA, settings.getNfoConstants().MPAA, movie.getMpaa());
			addIntNode(settings, doc, root, settings.getExportNodes().PLAY_COUNT, settings.getNfoConstants().PLAY_COUNT, movie.getPlayCount());
			addBooleanNode(settings, doc, root, settings.getExportNodes().WATCHED, settings.getNfoConstants().WATCHED, movie.isWatched(), TRUE_VALUE_STRING, FALSE_VALUE_STRING);
			addTextNode(settings, doc, root, settings.getExportNodes().ID, settings.getNfoConstants().ID, movie.getImdbId());
			addTextNode(settings, doc, root, settings.getExportNodes().TRAILER, settings.getNfoConstants().TRAILER, movie.getTrailer());
			addTextNode(settings, doc, root, settings.getExportNodes().GENRE, settings.getNfoConstants().GENRE, movie.getGenre());
			addTextNode(settings, doc, root, settings.getExportNodes().CREDITS, settings.getNfoConstants().CREDITS, movie.getCredits());
			addTextNode(settings, doc, root, settings.getExportNodes().DIRECTOR, settings.getNfoConstants().DIRECTOR, movie.getDirector());

			if(settings.getExportNodes().STREAM_DETAILS) {
				for(final StreamDetails sd : movie.getStreamDetails()) {
					final Node sdRoot = root.appendChild(doc.createElement(settings.getNfoConstants().STREAM_DETAILS));

					if(settings.getExportNodes().FILE_INFO) {
						final Node fileInfoRoot = sdRoot.appendChild(doc.createElement(settings.getNfoConstants().FILE_INFO));

						if(settings.getExportNodes().STREAM_DETAILS_VIDEO) {
							for(final Video vid : sd.getVideos()) {
								final Node vidRoot = fileInfoRoot.appendChild(doc.createElement(settings.getNfoConstants().STREAM_DETAILS_VIDEO));

								addTextNode(settings, doc, vidRoot, settings.getExportNodes().STREAM_DETAILS_VIDEO_CODEC,
										settings.getNfoConstants().STREAM_DETAILS_VIDEO_CODEC, vid.getCodec());
								addDoubleNode(settings, doc, vidRoot, settings.getExportNodes().STREAM_DETAILS_VIDEO_ASPECT,
										settings.getNfoConstants().STREAM_DETAILS_VIDEO_ASPECT, vid.getAspect());
								addIntNode(settings, doc, vidRoot, settings.getExportNodes().STREAM_DETAILS_VIDEO_WIDTH,
										settings.getNfoConstants().STREAM_DETAILS_VIDEO_WIDTH, vid.getWidth());
								addIntNode(settings, doc, vidRoot, settings.getExportNodes().STREAM_DETAILS_VIDEO_HEIGHT,
										settings.getNfoConstants().STREAM_DETAILS_VIDEO_HEIGHT, vid.getHeight());
							}
						}

						if(settings.getExportNodes().STREAM_DETAILS_AUDIO) {
							for(final Audio audio : sd.getAudios()) {
								final Node audioRoot = fileInfoRoot.appendChild(doc.createElement(settings.getNfoConstants().STREAM_DETAILS_AUDIO));

								addTextNode(settings, doc, audioRoot, settings.getExportNodes().STREAM_DETAILS_AUDIO_CODEC,
										settings.getNfoConstants().STREAM_DETAILS_AUDIO_CODEC, audio.getCodec());
								addIntNode(settings, doc, audioRoot, settings.getExportNodes().STREAM_DETAILS_AUDIO_CHANNELS,
										settings.getNfoConstants().STREAM_DETAILS_AUDIO_CHANNELS, audio.getChannels());
								addTextNode(settings, doc, audioRoot, settings.getExportNodes().STREAM_DETAILS_AUDIO_LANGUAGE,
										settings.getNfoConstants().STREAM_DETAILS_AUDIO_LANGUAGE, audio.getLanguage());
							}
						}

						if(settings.getExportNodes().STREAM_DETAILS_SUBTITLE) {
							for(final Subtitle sub : sd.getSubtitles()) {
								final Node subRoot = fileInfoRoot.appendChild(doc.createElement(settings.getNfoConstants().STREAM_DETAILS_SUBTITLE));

								addTextNode(settings, doc, subRoot, settings.getExportNodes().STREAM_DETAILS_SUBTITLE_LANGUAGE,
										settings.getNfoConstants().STREAM_DETAILS_SUBTITLE_LANGUAGE, sub.getLanguage());
							}
						}
					}
				}
			}

			if(settings.getExportNodes().ACTOR) {
				for(final Actor actor : movie.getActors()) {
					final Node actorRoot = root.appendChild(doc.createElement(settings.getNfoConstants().ACTOR));

					addTextNode(settings, doc, actorRoot, settings.getExportNodes().ACTOR_NAME, settings.getNfoConstants().ACTOR_NAME, actor.getName());
					addTextNode(settings, doc, actorRoot, settings.getExportNodes().ACTOR_ROLE, settings.getNfoConstants().ACTOR_ROLE, actor.getRole());
				}
			}
		}

		final TransformerFactory tf = TransformerFactory.newInstance();
		if(settings.getPrettyFormatNfo()) {
			tf.setAttribute("indent-number", new Integer(4));
		}

		final Transformer transformer;
		try {
			transformer = tf.newTransformer();
		} catch (TransformerConfigurationException e) {
			throw new NfoTransformerException("Failed to create transformer", e);
		}

		if(settings.getPrettyFormatNfo()) {
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		}

		final StreamResult result = new StreamResult(new StringWriter());
		final DOMSource source = new DOMSource(doc);

		try {
			transformer.transform(source, result);
		} catch (TransformerException e) {
			throw new NfoTransformerException("Failed to trasform nfo document", e);
		}

		return result.getWriter().toString();
	}

	private static void addTextNode(MovieSettings settings, Document doc, Node root, boolean exportNode, String nodeName, String value) {
		if(shouldExport(exportNode, value, settings)) {
			root.appendChild(doc.createElement(nodeName)).appendChild(doc.createTextNode(value == null ? "" : value));
		}
	}

	private static void addDoubleNode(MovieSettings settings, Document doc, Node root, boolean exportNode, String nodeName, Double value) {
		if(shouldExport(exportNode, value, settings)) {
			root.appendChild(doc.createElement(nodeName)).appendChild(doc.createTextNode(value == null ? "" : value.toString()));
		}
	}

	private static void addIntNode(MovieSettings settings, Document doc, Node root, boolean exportNode, String nodeName, Integer value) {
		if(shouldExport(exportNode, value, settings)) {
			root.appendChild(doc.createElement(nodeName)).appendChild(doc.createTextNode(value == null ? "" : value.toString()));
		}
	}

	private static void addBooleanNode(MovieSettings settings, Document doc, Node root, boolean exportNode, String nodeName, Boolean value, String trueValue, String falseValue) {
		if(shouldExport(exportNode, value, settings)) {
			root.appendChild(doc.createElement(nodeName)).appendChild(doc.createTextNode(value == null ? "" : value ? trueValue : falseValue));
		}
	}

	private static boolean shouldExport(boolean exportNode, String value, MovieSettings settings) {
		if(exportNode == false) {
			return false;
		} else if(value == null || value.trim().length() == 0) {
			return settings.getAddEmptyNfoNodes();
		}
		return true;
	}

	private static boolean shouldExport(boolean exportNode, Number value, MovieSettings settings) {
		if(exportNode == false) {
			return false;
		} else if(value == null) {
			return settings.getAddEmptyNfoNodes();
		}
		return true;
	}

	private static boolean shouldExport(boolean exportNode, Boolean value, MovieSettings settings) {
		if(exportNode == false) {
			return false;
		} else if(value == null) {
			return settings.getAddEmptyNfoNodes();
		}
		return true;
	}

}
